/*
 *   Copyright (C) 2010  Jens Kieselbach
 * 
 *   This program is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation, either version 3 of the License, or
 *   (at your option) any later version.
 * 
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 * 
 *   You may obtain a copy of the License at http://www.gnu.org/licenses/gpl.html
 */

package de.kieselbach.grootional.component

import com.jgoodies.forms.builder.*
import com.jgoodies.forms.layout.*
import de.kieselbach.io.*
import de.kieselbach.grootional.script.*
import de.kieselbach.grootional.util.*
import de.kieselbach.grootional.value.*
import de.kieselbach.sql.*
import de.kieselbach.swing.*
import de.kieselbach.swing.border.*
import de.kieselbach.swing.table.*
import de.kieselbach.swing.text.*
import de.kieselbach.swing.validation.*
import java.sql.*
import java.util.logging.*
import java.text.*
import javax.swing.*
import javax.swing.event.*
import javax.swing.tree.*

import java.awt.event.*
import java.awt.BorderLayout
import java.awt.Color
import java.awt.Dimension
import java.awt.Font
import java.awt.Graphics
import java.awt.Point

import jsyntaxpane.DefaultSyntaxKit

class SqlEditorCard extends Card { 

    def databaseFrame
    def frame
    def uiResources
    def resourceBundle
    def properties

    def connection

    def pathLabel
    def cursorLabel
    def encodingLabel

    def executeButton
    def newButton
    def openButton
    def saveButton
    def saveAsButton

    def textFile
    def textArea
    def resultPane
	def output
	
    SqlEditorCard(def dbframe) {

        super(new BorderLayout())
        processable = true
        addToolBarGlue = false

        databaseFrame = dbframe
        frame = dbframe.frame
        uiResources = dbframe.uiResources
        resourceBundle = uiResources.resourceBundles['gui']
        properties = dbframe.properties

        connection = dbframe.connection

        title = resourceBundle.getString('sqlEditor.title')
        icon = uiResources.images['sql16'] as Icon

        textFile = new TextFile()
        if(properties['lastDirectory'] != null) {
            try {
                textFile.lastDirectory = new File(properties['lastDirectory'])
            }
            catch(Exception exc) {
                Logger.getLogger(getClass().name).log(Level.WARNING, 'Could not last directory!', exc)
            }
        }
        textFile.suffixDescription = resourceBundle.getString('sqlEditor.sqlFiles')
        textFile.suffixes << 'sql'
        textFile.encoding = properties['fileEncoding']
        if(textFile.encoding == null) {
            textFile.encoding = 'UTF-8'
        }
        
        executeButton = new FlatButton(uiResources.images['execute16'])
        executeButton.enabled = true
        executeButton.toolTipText = resourceBundle.getString('sqlEditor.execute')
        toolBarItems << executeButton
        executeButton.actionPerformed = {
            execute()
        }

        toolBarItems << new CardSeparator()

        newButton = new FlatButton(uiResources.images['new16'])
        newButton.toolTipText = resourceBundle.getString('sqlEditor.new')
        toolBarItems << newButton
        newButton.actionPerformed = {
            clear()
        }

        openButton = new SplitButton(uiResources.images['open16'])
        openButton.toolTipText = resourceBundle.getString('sqlEditor.open')
        toolBarItems << openButton
        openButton.menuGetter = {
            getReopenPopup()
        }

        openButton.actionPerformed = {
            open()
        }

        saveButton = new FlatButton(uiResources.images['save16'])
        saveButton.enabled = false
        saveButton.toolTipText = resourceBundle.getString('sqlEditor.save')
        toolBarItems << saveButton
        saveButton.actionPerformed = {
            save()
        }

        saveAsButton = new FlatButton(uiResources.images['saveas16'])
        saveAsButton.toolTipText = resourceBundle.getString('sqlEditor.saveas')
        toolBarItems << saveAsButton
        saveAsButton.actionPerformed = {
            save(true)
        }

        toolBarItems << Box.createHorizontalGlue()

        // ---------------------------------------------------------------------------

        DefaultSyntaxKit.initKit()

        textArea = new PlainEditorPane()
        def scroll = new JScrollPane(textArea)

        textArea.setContentType("text/sql");
        textArea.setFont(new Font("Monospaced", Font.PLAIN, 12))

        def status = new JPanel(new BorderLayout(0, 0))
        status.border = BorderFactory.createCompoundBorder(CustomLineBorder.create(UIUtilities.lineBorderColor, '426'), BorderFactory.createEmptyBorder(0, 4, 0, 0))
        def box = Box.createHorizontalBox()
        status.add(box, BorderLayout.CENTER)

        pathLabel = new JLabel('*')
		pathLabel.font = pathLabel.font.deriveFont(11 as float)

        cursorLabel = new FlatButton('1 : 1')
        cursorLabel.focusPainted = false
		cursorLabel.font = cursorLabel.font.deriveFont(11 as float)
        cursorLabel.actionPerformed = {
            gotoLine()
        }

        encodingLabel = new FlatButton(textFile.encoding)
        encodingLabel.focusPainted = false
		encodingLabel.font = encodingLabel.font.deriveFont(11 as float)
        encodingLabel.actionPerformed = {
            selectEncoding()
        }

        box.add(pathLabel)
        box.add(Box.createHorizontalGlue())
        box.add(new CardSeparator(6, cursorLabel.preferredSize.@height))
        box.add(cursorLabel)
        box.add(new CardSeparator(6, cursorLabel.preferredSize.@height))
        box.add(encodingLabel)

        // ---------------------------------------------------------------------------

        def areapanel = new JPanel(new BorderLayout(0, 0))
        areapanel.add(scroll, BorderLayout.CENTER)
        areapanel.add(status, BorderLayout.SOUTH)
		areapanel.border = BorderFactory.createEmptyBorder(0, 0, 1, 0)
		
        // ---------------------------------------------------------------------------

        resultPane =  new TabbedCardPane(frame, uiResources, true, 1, 0, 0, 0)

		output = new OutputCard(databaseFrame)
        resultPane.addCard(output, true)

        // ---------------------------------------------------------------------------


        def splitpane = new CleanSplitPane(JSplitPane.VERTICAL_SPLIT, true, areapanel, resultPane)
        splitpane.resizeWeight = 0.7d

        add(splitpane, BorderLayout.CENTER)

        // ---------------------------------------------------------------------------

        textArea.caretUpdate = {e ->
            def line = textArea.getLineOfOffset(e.dot)
            def column = e.dot - textArea.getLineStartOffset(line)
            cursorLabel.text =  (line + 1) + ' : ' + (column + 1)
        }

        textArea.document.addDocumentListener([
            insertUpdate: {modified = true},
            changeUpdate: {modified = true},
            removeUpdate: {modified = true}
        ] as DocumentListener)

        textArea.registerKeyboardAction({execute()} as ActionListener, KeyStroke.getKeyStroke('control ENTER'), WHEN_FOCUSED)
        textArea.registerKeyboardAction({gotoLine()} as ActionListener, KeyStroke.getKeyStroke('control L'), WHEN_FOCUSED)
        textArea.registerKeyboardAction({clear()} as ActionListener, KeyStroke.getKeyStroke('control N'), WHEN_FOCUSED)
        textArea.registerKeyboardAction({open()} as ActionListener, KeyStroke.getKeyStroke('control O'), WHEN_FOCUSED)
        textArea.registerKeyboardAction({save()} as ActionListener, KeyStroke.getKeyStroke('control S'), WHEN_FOCUSED)
        textArea.registerKeyboardAction({save(true)} as ActionListener, KeyStroke.getKeyStroke('shift control S'), WHEN_FOCUSED)
    }

    // --------------------------------------------------------------

    void dispose() {
        
        if(textFile.lastDirectory != null) {
            properties['lastDirectory'] = textFile.lastDirectory.toString()
        }

        if(textFile.encoding != null) {
            properties['fileEncoding'] = textFile.encoding
        }

        if(textFile.selectedFile != null) {
            databaseFrame.addRecentFile(textFile.selectedFile)
        }
    }

    void enableFields() {

        executeButton.enabled = true
        newButton.enabled = true
        openButton.enabled = true
        saveButton.enabled = textFile.selectedFile != null
        saveAsButton.enabled = true
        textArea.enabled = true
    }

    void disableFields() {

        executeButton.enabled = false
        newButton.enabled = false
        openButton.enabled = false
        saveButton.enabled = false
        saveAsButton.enabled = false
        textArea.enabled = false
    }

    void clear() {

        if(textFile.selectedFile != null) {
            databaseFrame.addRecentFile(textFile.selectedFile)
        }

        textFile.selectedFile = null
        pathLabel.text = '*'
        textArea.text = ''
        modified = false
        enableFields()
    }

    void selectEncoding() {

        def popup = new JPopupMenu()
        resourceBundle.getString('sqlEditor.encodings').tokenize(',').each() {enc ->
            def menu = new JMenuItem(enc)
            popup.add(menu)
            menu.actionPerformed = {
                textFile.encoding = enc
                encodingLabel.text = enc
            }
        }
        popup.pack()
        popup.show(encodingLabel, encodingLabel.width - popup.preferredSize.@width, 0 - popup.preferredSize.@height)
    }

    void gotoLine() {

        int count = textArea.lineCount
        def checker = {comp ->
            def num = comp.text
            if(num != null && num.isInteger()) {
                int n = Integer.parseInt(num)
                return n > 0 && n <= count
            }
            return false
        }

        def valid = new Validator()
        def field = valid.validate(new JTextField(), checker)

        if(new PaneBuilder().plain(title, message: MessageFormat.format(resourceBundle.getString('sqlEditor.enterLineNumber'), count), input: field, focus: field, owner: frame, validator: valid, options: ['ok', 'cancel'], default: 'ok') == 'ok') {
            textArea.setCaretPosition(textArea.getLineStartOffset(Integer.parseInt(field.text)-1))
            textArea.requestFocus()
        }
    }

    def getReopenPopup() {

        if(databaseFrame.recentFiles.size() > 0) {
            def popup = new JPopupMenu()
            databaseFrame.recentFiles.each() {file ->

                def menu = new JMenuItem(file.name)
                popup.add(menu)
                menu.actionPerformed = {
                    open(file)
                }
            }
            popup.pack()
            return popup
        }
        else {
            SwingUtilities.invokeLater {
                open()
            }
        }
    }

    void open(def file = null) {

        def backup = textFile.selectedFile
        if(file != null || textFile.choose()) {

            if(file != null) {
                textFile.selectedFile = file
            }

            disableFields()
            startProcessing()

            Thread.start() {
                def result

                try {
                    def text = textFile.open()

                    result = {
                        pathLabel.text = textFile.selectedFile.toString()
                        textArea.text = text
                        textArea.caretPosition = 0
                        modified = false

                        if(backup != null) {
                            databaseFrame.addRecentFile(backup)
                        }
                    }
                }
                catch(Exception exc) {
                    result = {
                        textFile.selectedFile = backup
                        new PaneBuilder().error(title, message: resourceBundle.getString('sqlEditor.openFailed'), owner: frame, exception: exc)
                    }
                }

                SwingUtilities.invokeLater {

                    stopProcessing()
                    enableFields()

                    if(result != null) {
                        result()
                    }
                }
            }
        }
        else {
            textFile.selectedFile = backup
        }
    }

    void save(boolean saveas = false) {

        def backup = textFile.selectedFile
        if(saveas) {
            def check = {file ->
                new PaneBuilder().question(title, message: MessageFormat.format(resourceBundle.getString('sqlEditor.overrideConfirmation'), file.name), owner: frame, options: ['yes', 'no'], default: 'no') == 'yes'
            }

            textFile.choose(true, check)
        }

        if(textFile.selectedFile) {

            disableFields()
            startProcessing()

            Thread.start() {
                def result

                try {
                    pathLabel.text = textFile.selectedFile.toString()
                    textFile.save(textArea.text)

                    result = {
                        modified = false
                    }
                }
                catch(Exception exc) {
                    result = {
                        textFile.selectedFile = backup
                        new PaneBuilder().error(title, message: resourceBundle.getString('sqlEditor.saveFailed'), owner: frame, exception: exc)
                    }
                }

                SwingUtilities.invokeLater {

                    stopProcessing()
                    enableFields()

                    if(result != null) {
                        result()
                    }
                }
            }
        }
        else {
            textFile.selectedFile = backup
        }
    }

    // --------------------------------------------------------------------------------------

    void execute() {

		def log = {text ->
			output.append(text)
		}
		
        def exe = new ScriptExecutor(connection, textArea.text, log)
		exe.execute()
    }
}

